package com.cz.tcpclientmaster;

import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;

import com.cz.tcpclientmaster.exception.ClientOffLineException;
import com.cz.tcpclientmaster.util.LogUtil;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * @author 陈章
 * create at 2021/1/19 14:14
 * desc:Tcp客户端核心类
 */
public class TcpSocketClient {
    private static final String TAG = "TcpSocketClient";
    private final Handler childHandler;
    private Socket socket;
    private OnClientStatusListener onClientStatusListener;
    /**
     * 主动断开标记
     */
    private  boolean runFlag;
    private Transceiver transceiver;
    /**
     * 是否是异常，导致被动关闭客户端。
     */
    private boolean isExceptionClosedClient = false;

    /**
     * 是否自动重连
     */
    private boolean autoReconnect = false;

    /**
     * 自动重连的间隔(毫秒)
     */
    private int autoReconnectRateTimeMillis = 2000;
    private WeakReference<Context> mContext;

    private final int MSG_TYPE_CONNECT = 10;
    private final int MSG_TYPE_SEND = 20;
    private final int MSG_TYPE_STOP = 30;
    private final int MSG_TYPE_RELEASE = 40;
    private ConnectModel connectModel;
    private final ReconnectController reconnectController;

    /**
     * 该callback运行于子线程
     */
    private class ChildCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            reconnectController.handleMessage(msg);

            switch (msg.what){
                case MSG_TYPE_CONNECT:
                    if(onClientStatusListener != null){
                        onClientStatusListener.onConnecting();
                    }
                    ConnectModel connectModel = (ConnectModel) msg.obj;
                    socket = new Socket();
                    try {
                        //keep alive作用：避免对方宕机，还保持着连接，占用资源。https://blog.csdn.net/nanhuaibeian/article/details/104760165/
                        socket.setKeepAlive(true);
                        runFlag = true;
                        isExceptionClosedClient = false;

                        socket.connect(new InetSocketAddress(connectModel.serverIp, connectModel.serverPort), connectModel.connectTimeOutMillis); //连接请求超时时间10s
                        transceiver = new Transceiver(socket);
                        new Thread(transceiver).start();

//                        new CheckAliveThread().start();
                    } catch (IOException e) {
                        e.printStackTrace();
                        callbackConnectFailed(e);
                    }
                    break;
                case MSG_TYPE_SEND:
                    String value = (String) msg.obj;
                    if(transceiver != null){
                        transceiver.sendPrivate(value);
                    }
                    break;
                case MSG_TYPE_STOP:
                    doStop();
                    break;
                case MSG_TYPE_RELEASE:
                    doStop();
                    //关闭Handler轮循
                    childHandler.removeCallbacksAndMessages(null);
                    childHandler.getLooper().quit();
                    break;
            }
            return false;
        }

        private void doStop() {
            if (transceiver != null) {
                transceiver.stop();
                transceiver = null;
            }
        }
    }


    /**
     * 收发机
     */
    private class Transceiver  implements Runnable{
        private   InputStream in;
        private   BufferedWriter out;
        private Socket socket;

        public Transceiver(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                in = this.socket.getInputStream() ;
                out = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()) );
            } catch (IOException e) {
                e.printStackTrace();
                callbackError(e);
            }
            callbackConnected();

            while (runFlag) {
                //如何自己主动断了会报错java.net.SocketException: Socket closed
                if(onClientStatusListener != null){
                    try {
                        //此处为什么in要判断null：因为stop后，runFlag为false了，但是最后一次for循环可能还在执行，这时in为null。
                        if(in != null && in.available() > 0){
                            onClientStatusListener.onRead(in);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            if(!isExceptionClosedClient){
                callbackDisconnected();
            }
        }

        private void sendPrivate(String data) {
            try {
                LogUtil.d(TAG, ">>发送客户端数据:" + data);
                out.write(data);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
                callbackSendMsgFailure(e);
            }
        }

        public void stop(){
            runFlag = false;
            try {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
                if (socket != null) {
                    socket.close();
                }

                in = null;
                out = null;
                socket = null;
            } catch (IOException e) {
                e.printStackTrace();
                callbackOnDisconnect(e);
            }
        }

    }



    /**
     * 检查连接是否存活线程
     */
    private class CheckAliveThread extends Thread{
        @Override
        public void run() {
            while (TcpSocketClient.this.isAlive()){
                SystemClock.sleep(500);
            }
            exceptionCloseClient(new ClientOffLineException());
        }
    }

    private class ConnectModel{
        public  String serverIp;
        public  int serverPort;
        public int connectTimeOutMillis;

        public ConnectModel(String serverIp, int serverPort, int connectTimeOutMillis) {
            this.serverIp = serverIp;
            this.serverPort = serverPort;
            this.connectTimeOutMillis = connectTimeOutMillis;
        }
    }

    public TcpSocketClient(Context mContext) {
        this.mContext = new WeakReference(mContext);
        HandlerThread handlerThread = new HandlerThread("tcp_client " + this);
        handlerThread.start();
        //子线程Handler
        childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());

        reconnectController = new ReconnectController(this, childHandler);
    }

    public void setAutoReconnect(boolean autoReconnect) {
        this.autoReconnect = autoReconnect;
    }

    public void setAutoReconnectRateTimeMillis(int autoReconnectRateTimeMillis) {
        this.autoReconnectRateTimeMillis = autoReconnectRateTimeMillis;
    }

    public void connect(String serverIp, int serverPort){
        this.connect(serverIp, serverPort,10000);
    }

    public void connect(String serverIp,int serverPort, int connectTimeOutMillis){
        reconnectController.stopRunningTask();

        connectModel = new ConnectModel(serverIp, serverPort, connectTimeOutMillis);
        childHandler.obtainMessage(MSG_TYPE_CONNECT, connectModel).sendToTarget();
    }

    /**
     * 重新连接，不对SDK外提供接口。
     */
    void reconnect(){
        childHandler.obtainMessage(MSG_TYPE_CONNECT, connectModel).sendToTarget();
    }

    public void send(String data) {
        childHandler.obtainMessage(MSG_TYPE_SEND,data).sendToTarget();
    }

    public void stop(){
        childHandler.obtainMessage(MSG_TYPE_STOP).sendToTarget();
    }

    /**
     * 异常关闭释放客户端
     * @param e
     */
    public void exceptionCloseClient(Throwable e){
        if(runFlag){
            isExceptionClosedClient  = true;
            stop();
            callbackOnDisconnect(e);
            if(isAutoReconnect()){
                reconnectController.reconnect();
            }
        }
    }

    public void release(){
        childHandler.obtainMessage(MSG_TYPE_RELEASE).sendToTarget();
    }

    /**
     * 检察客户端是否还连接着
     * @return
     */
    public boolean isAlive(){
        try{
            //发送1个字节的紧急数据，默认情况下，服务器端没有开启紧急数据处理，不影响正常通信
            socket.sendUrgentData(0xFF);
            return true;
        }catch(Exception se){
            return false;
        }
    }

    public void setOnClientStatusListener(OnClientStatusListener onClientStatusListener) {
        this.onClientStatusListener = onClientStatusListener;
    }

    private void callbackOnDisconnect(Throwable e) {
        if (onClientStatusListener != null) {
            onClientStatusListener.onDisconnect(e);
        }
    }

    private void callbackSendMsgFailure(IOException e) {
        if(onClientStatusListener != null){
            onClientStatusListener.onSendMsgFailure(e);
        }
    }

    private void callbackDisconnected() {
        if (onClientStatusListener != null) {
            onClientStatusListener.onDisconnected();
        }
    }

    private void callbackConnected() {
        if(onClientStatusListener != null){
            onClientStatusListener.onConnected();
        }
    }

    private void callbackError(IOException e) {
        if(onClientStatusListener != null){
            onClientStatusListener.onError(e);
        }
    }

    private void callbackConnectFailed(IOException e) {
        if(onClientStatusListener != null){
            onClientStatusListener.onConnectFailed(e);
        }
    }

    public interface OnClientStatusListener{
        /**
         * 接收数据
         * @param in
         */
        void onRead(InputStream in);

        /**
         * 正在连接
         */
        void onConnecting();

        /**
         * 已经连接
         */
        void onConnected();

        /**
         * 连接失败
         * 1)可能服务未开启
         * @param e
         */
        void onConnectFailed(Throwable e);

        /**
         * 其它异常
         * @param e
         */
        void onError(Throwable e);

        /**
         * 连接异常断开
         * @param e
         */
        void onDisconnect(Throwable e);

        /**
         * 连接正常断开
         */
        void onDisconnected();

        /**
         * 发送消息失败
         * @param e
         */
        void onSendMsgFailure(Throwable e);
    }

    public Context getContext() {
        return mContext.get();
    }

    public boolean isAutoReconnect() {
        return autoReconnect;
    }

    public int getAutoReconnectRateTimeMillis() {
        return autoReconnectRateTimeMillis;
    }
}
